using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;
using System.IO;
using System.Diagnostics;

using Point = System.Drawing.Point;
using Acap = Autodesk.AutoCAD.ApplicationServices.Application;
using JoinBox.WPF.多文档标签.ViewModel;
using JoinBox.BasalWindows;
using static Win32API.WinApi;
using RemoteAccess;

namespace JoinBox.WPF.多文档标签.View
{
    /// <summary>
    /// 嵌入WPF
    /// </summary>
    /// <typeparam name="WPFUserControl">传入来构造和嵌入的WPF</typeparam>
    public class Anchor<WPFUserControl> : JAnchor, IDisposable
    {
        #region 成员
        // cad主窗口句柄
        private IntPtr _Handle_AC;
        // cad文档管理器窗口句柄
        private IntPtr _Handle_Mdi;
        // 文档栏会比挤出更快进入程序,所以需要这个标记判断挤出成功
        // 否则计算文档高度不正确 DocSizeTool.TiaoZheng()
        static bool _SetTopOK = false;
        //文档的WPF控件
        private DocWindow _DocWindow;
        //键盘钩子
        Win32API.KeyboardHook _KeyboardHook;

        //子类化拦截消息,声明写这里防止GC释放
        NativeCallProc _AcadAppCallWProc;
        NativeCallProc _AcadMdiCallWProc;
        #endregion

        /// <summary>
        /// 嵌入WPF
        /// </summary>
        public Anchor()
        {
            #region 子类化拦截消息_cad程序
            //Acad主窗口的句柄
            try
            {
                _Handle_AC = Acap.MainWindow.Handle;
                if (_Handle_AC == IntPtr.Zero)
                {
                    throw new Exception("无法获取Acad主窗体句柄,子类化操作失败,程序退出");
                }
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
                Process.GetCurrentProcess().Kill();
            }

            _AcadAppCallWProc = new NativeCallProc(_Handle_AC);

            //57600_new || 18_qnew 对应cad命令
            IntPtr _new = (IntPtr)57600;
            IntPtr _qnew = (IntPtr)18;
            IntPtr _chongZhi = (IntPtr)0x0001e100;//重置之后ctrl+n取得的

            _AcadAppCallWProc.WndProc(m => {
                try
                {
                    var msg = (WM)m.Msg;

#if true3
                    //鼠标按着文档窗口移动时候触发 WM.WM_MOUSEACTIVATE
                    //https://social.msdn.microsoft.com/Forums/en-US/572cf1ac-c55f-47cb-b41c-bbc0d7fd23b8/wndproc-detect-title-bar-clicked

                    //这里是可用的,但是拖动文档窗口会频繁发生,拦截每个文档栏的拖动又会产生消息通知滞后,所以不想做了
                    //拖动窗口/最大化/还原等尺寸改变时候触发,触发比WM_SIZE多
                    //调整cad文档尺寸
                    //这里只是拦截cad主窗口改变大小
                    if (msg == WM.WM_WINDOWPOSCHANGED)
                    {
                        DocSizeTool.TiaoZheng();
                    }
#endif
                    //移动cad界面时候触发
                    if (msg == WM.WM_MOVE)
                    {
                        this.RefreshPos();
                    }
                    else if (msg == WM.WM_COMMAND)
                    {
                        //这里拦截一下两种发送方式
                        //WinApi.SendMessage(Acap.MainWindow.Handle, (int)MsgType.WM_COMMAND, (IntPtr)18, IntPtr.Zero);//qnew
                        //WinApi.SendMessage(Acap.MainWindow.Handle, (int)MsgType.WM_COMMAND, (IntPtr)57600, IntPtr.Zero);//new
                        if (m.WParam == _new || m.WParam == _qnew || m.WParam == _chongZhi)
                        {
                            // 之后是阻止cad接收win32的ctrl+n消息,需要键盘钩子hook
                            // 从而防止发送消息引起的致命错误
                            this._DocWindow.Qnew();
                            return false;
                        }
                    }
                    AutoDocs.SyncManager?.Loaded();
                }
                catch //cad崩溃的时候会触发
                {
                    Debugger.Break();
                }
                return true;
            });
            #endregion

            #region 子类化拦截消息_文档管理器,挤出工具条的空间,令其他空间缩小
            //活动文档父窗用于存放多文档栏
            _Handle_Mdi = GetParent(Acap.DocumentManager.MdiActiveDocument.Window.Handle);

            _AcadMdiCallWProc = new NativeCallProc(_Handle_Mdi);
            _AcadMdiCallWProc.WndProc(m => {
                try
                {
                    var msg = (WM)m.Msg;
                    if (msg == WM.WM_SIZE || msg == WM.WM_MOVE)
                    {
                        this.RefreshPos();
                    }
                    else if (msg == WM.WM_NCCALCSIZE)
                    {
                        if (m.WParam != IntPtr.Zero)
                        {
                            //nc消息要算窗口大小,预留一个空间出来放窗口,它可以缩小其他窗口的空间,而不是把WPF遮蔽到上面去
                            //非托管内存块->托管对象
                            var str = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));
                            //预留高度,如果不-2会超出来一点点
                            str.rgrc[0].Top = str.rgrc[0].Top + Convert.ToInt32(DocumentTab.HeightDPI - 2);
                            //托管对象->非托管内存块
                            Marshal.StructureToPtr(str, m.LParam, true);
                            _SetTopOK = true;
                        }
                    }
                }
                catch //cad崩溃的时候会触发
                { }
                return true;
            });
            #endregion

            #region 键盘钩子
            _KeyboardHook = new Win32API.KeyboardHook();
            _KeyboardHook.KeyDownEvent += (sender, e) => {
                //ctrl+n替换,减少原生命令的致命错误
                if (e.KeyCode == Keys.N
                    && System.Windows.Forms.Control.ModifierKeys == Keys.Control
                    && GetForegroundWindow() == _Handle_AC
                    && _DocWindow != null)
                {
                    _DocWindow.Qnew();
                    ((Win32API.KeyboardHook)sender).IsHookBreak = true;
                };
            };
            #endregion

            base.ThreadDoAction(() => {
                while (!Disposed)
                {
                    //cad界面完成 && 挤出空间成功 && 程序空闲时候,上下文准备就绪
                    if (Acap.IsQuiescent && _SetTopOK && AutoDocs.SyncManager.SyncContext.Context != null)
                    {
                        var wpf = JoinBox.BasalCurrency.CreateClass.GetService<WPFUserControl>();
                        _DocWindow = wpf as DocWindow;
                        if (_DocWindow == null)
                        {
                            return;
                        }
                        var str = _DocWindow?.ToString();
                        Thread.CurrentThread.Name = $"WPF_Thread_{str}";

                        //设置一次初始化值先,再加入容器
                        EmbedDoWPF = new EmbedWPF($"EmbedWPF_{str}", _Handle_AC);
                        SetSizeWPF();

                        //CAD界面没有完成会导致下面两句卡死
                        EmbedDoWPF.Add(_DocWindow);//HwndSource卡死
                        RefreshPos();              //moveWindow卡死
                        DragDrop(_DocWindow);
                        break;
                    }
                    else
                    {
                        Thread.Sleep(200);
                    }
                }
            });

            ///加载时候分为两种,一直是注册表加载,会令窗口刷新,另一种是netload命令,执行的时候已经加载了窗口,
            ///所以不会触发 <see cref="WM.WM_NCCALCSIZE">刷新窗口计算</see> 需要引发一下.
            //SetWindowPos(Acap.DocumentManager.MdiActiveDocument.Window.Handle);
            //SetWindowPos(_Handle_AC);
            SetWindowPos(_Handle_Mdi);
        }

        /// <summary>
        /// 拖放操作
        /// </summary>
        /// <param name="control"></param>
        void DragDrop(System.Windows.Controls.Control control)
        {
            if (control == null)
            {
                throw new ArgumentNullException(nameof(control));
            }
            new ControlDragDrop(control, (CDD_sender, CDD_e) => {
                try
                {
                    string path = ((Array)CDD_e.Data.GetData(System.Windows.DataFormats.FileDrop))?.GetValue(0)?.ToString();
                    if (path == null)
                    {
                        AutoDocs.WriteMessage("**拖拉的路径失败" + Environment.NewLine);
                        return;
                    }

                    string extension = Path.GetExtension(path).ToLower();//扩展名
                    if (extension == ".dll")
                    {
                        AutoDocs.WriteMessage("**执行加载dll操作" + Environment.NewLine);

                        var ad = new AssemblyDependent(path);
                        ad.CurrentDomainAssemblyResolveEvent +=
                            RunTimeCurrentDomain.DefaultAssemblyResolve; //默认都需要这个
                        ad.Load();
                        if (!ad.LoadOK)
                        {
                            AutoDocs.WriteMessage(ad.LoadErrorMessage);
                            return;
                        }
                        AutoDocs.WriteMessage("加载成功!");
                    }
                    else if (extension == ".dwg")
                    {
                        //前台打开图纸
                        AutoDocs.SyncManager.SyncContext.Post(() => {
                            Opendwg(path);
                        });
                    }
                    else
                    {
                        AutoDocs.WriteMessage("**你拖拉的是什么玩意,不支持加载" + Environment.NewLine);
                    }
                }
                catch (Exception ee)
                {
                    AutoDocs.WriteMessage("\n**加载出现错误::" + Environment.NewLine + ee.Message + Environment.NewLine);
                }
            });
        }

        /// <summary>
        /// 在cad的前台打开dwg
        /// </summary>
        /// <param name="strFileName"></param>
        public static bool Opendwg(string strFileName)
        {
            var dm = Acap.DocumentManager;
            if (!File.Exists(strFileName))
            {
                dm.MdiActiveDocument.Editor.WriteMessage("此文件不存在: " + strFileName);
                return false;
            }
            try
            {
#if NET35
                dm.Open(strFileName, false);
#elif NET40
                Autodesk.AutoCAD.ApplicationServices.DocumentCollectionExtension.Open(dm, strFileName, false);
#else
                dm.AppContextOpenDocument(strFileName);
#endif
            }
            catch (System.Exception e)
            {
                dm.MdiActiveDocument.Editor.WriteMessage("\n此文件打开错误: " + strFileName + "\n错误信息:" + e.Message);
                return false;
            }
            return true;
        }

        /// <summary>
        /// 设置WPF大小
        /// </summary>
        void SetSizeWPF()
        {
            if (EmbedDoWPF == null)
            {
                return;
            }

            //返回客户区大小
            GetClientRect(_Handle_AC, out RECT lpRect);

            //客户区左上角转为屏幕坐标
            var lpPoint = new Point(lpRect.Left, lpRect.Top);
            ClientToScreen(_Handle_AC, ref lpPoint);

            //返回cad文档管理器窗口边框尺寸
            GetWindowRect(_Handle_Mdi, out RECT rect2);

            //窗口大小
            int rect2_Width = rect2.Right - rect2.Left;

            var x = rect2.Left - lpPoint.X;
            var y = rect2.Top - lpPoint.Y;

            //左上角点开始,此处只作为初始化值,并没有移动的功能.
            EmbedDoWPF.Parameters.SetPosition(x, y);

            //设置大小(无法实时刷新宽度,所以第一次就弄个屏幕长,然后会被cad主窗口截断不需要的部分)
            // _EmbedWPF.Parameters.SetSize(rect2_Width + 1000, rect2_Height);

            //获得当前屏幕的大小
            var allScreensRect = Screen.AllScreens[0].WorkingArea;
            EmbedDoWPF.Parameters.SetSize(allScreensRect.Width, DocumentTab.HeightDPI);

            if (EmbedDoWPF.Handle != IntPtr.Zero)
            {
                //用win32的方法实现移动, 这里高度增加,会令整体高度拉高,而控件在中间
#if true
                var rec = new RECT(x, y, x + rect2_Width, y + DocumentTab.HeightDPI);
#else
                var rec = new RECT(x, 0, x + rect2_Width, WPF_Height);
#endif
                MoveWindow(EmbedDoWPF.Handle, rec);
            }
        }

        /// <summary>
        /// 刷新位置
        /// </summary>
        public void RefreshPos()
        {
            //暂停刷新,用来移除多文档栏
            if (Disposed)
                return;
            SetSizeWPF();
            EmbedDoWPF?.SetPos();
        }

        #region Dispose
        public new bool Disposed { private set; get; } = false;

        /// <summary>
        /// 显式调用Dispose方法,继承IDisposable
        /// </summary>
        public new void Dispose()
        {
            //由手动释放
            Dispose(true);
            //通知垃圾回收机制不再调用终结器(析构器)_跑了这里就不会跑析构函数了
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 析构函数,以备忘记了显式调用Dispose方法
        /// </summary>
        ~Anchor()
        {
            //由系统释放
            Dispose(false);
        }

        //https://kb.cnblogs.com/page/97871/
        /// <summary>
        /// 释放
        /// </summary>
        /// <param name="ing"></param>
        protected new virtual void Dispose(bool ing)
        {
            if (Disposed)
            {
                //不重复释放
                return;
            }
            //让类型知道自己已经被释放
            Disposed = true;

            //卸载钩子
            _KeyboardHook?.Dispose();
            _KeyboardHook = null;

            //释放窗体,在-duo的时候有用
            _AcadAppCallWProc?.Dispose();
            _AcadAppCallWProc = null;
            _AcadMdiCallWProc?.Dispose();
            _AcadMdiCallWProc = null;

            //排序窗口,触发刷新
            SetWindowPos(_Handle_Mdi);

            //卸载父类的
            base.Dispose();
        }
        #endregion
    }
}
